/**
 * ****************************************************************************
 * Project: RoboBridge
 * ----------------------------------------------------------------------------
 * -------------------------- [c]2012 - KrawallMann ---------------------------
 * ---------------------- http://www.krawallmann.de.tc/ -----------------------
 * ****************************************************************************
 * File: RoboBridge.java Package: tc.de.krawallmann.robobridge Version: 1.0
 * Author(s): KrawallMann
 * ****************************************************************************
 * Description:
 * ****************************************************************************
 */
package tc.de.krawallmann.robobridge;

import java.io.IOException;
import java.util.logging.Level;

import tc.de.krawallmann.robobridge.communication.AbstractCommunicationInterface;
import tc.de.krawallmann.robobridge.communication.AbstractRemoteCommunicationDevice;
import tc.de.krawallmann.robobridge.communication.Command;
import tc.de.krawallmann.robobridge.communication.CommandType;
import tc.de.krawallmann.robobridge.communication.protocol.RoboBridgeProtocol;
import tc.de.krawallmann.robobridge.devices.Robot;
import tc.de.krawallmann.robobridge.util.BitOperations;
import tc.de.krawallmann.robobridge.util.Log;

/**
 * Use this class in your program - it helps you to easily create your PC based
 * program for your RP6.
 * 
 * @param <T>
 *            the generic type
 * @author KrawallMann
 * @version 1.0
 */
public class RoboBridge<T extends AbstractRemoteCommunicationDevice> implements Runnable {

	/** The communication interface. */
	private transient final AbstractCommunicationInterface<T> communicationInterface;

	/** The do run. */
	private transient boolean doRun;

	private boolean executingProgram = true;

	/** The program. */
	private transient Program program;

	/** The protocol. */
	private transient RoboBridgeProtocol protocol;

	/** The robot. */
	private transient final Robot robot;

	/** The sensor update interval. */
	private transient long sensorUpdateInterval;

	/**
	 * Instantiates a new robo bridge.
	 * 
	 * @param communicationInterface
	 *            the communication interface
	 */
	public RoboBridge(final AbstractCommunicationInterface<T> communicationInterface) {
		super();
		this.communicationInterface = communicationInterface;
		this.robot = new Robot();
		this.protocol = new RoboBridgeProtocol(communicationInterface, this);
	}

	/**
	 * Close.
	 */
	public void close(int reason) {
		program.close();
		doRun = false;
		robot.discardAllCommands();
		communicationInterface.close(reason);
		protocol.close();
		Log.log(Level.INFO, getClass(), "close", "Everything closed!");
		protocol = new RoboBridgeProtocol(communicationInterface, this);
	}

	/**
	 * Start.
	 * 
	 * @param device
	 *            the device
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public void connect(final T device) throws IOException {
		communicationInterface.connect(device);
		protocol.open();
	}

	/**
	 * Gets the communication interface.
	 * 
	 * @return the communication interface
	 */
	public AbstractCommunicationInterface<T> getCommunicationInterface() {
		return this.communicationInterface;
	}

	/**
	 * Gets the program.
	 * 
	 * @return the program
	 */
	public Program getProgram() {
		return this.program;
	}

	/**
	 * Gets the protocol.
	 * 
	 * @return the protocol
	 */
	public RoboBridgeProtocol getProtocol() {
		return this.protocol;
	}

	/**
	 * Gets the robot.
	 * 
	 * @return the robot
	 */
	public Robot getRobot() {
		return this.robot;
	}

	/**
	 * Gets the sensor update interval.
	 * 
	 * @return the sensor update interval
	 */
	public long getSensorUpdateInterval() {
		return this.sensorUpdateInterval;
	}

	/**
	 * Checks if is connected.
	 * 
	 * @return true, if is connected
	 */
	public boolean isConnected() {
		return this.communicationInterface.isConnected();
	}

	public boolean isExecutingProgram() {
		return executingProgram;
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
	@Override
	public void run() {
		while (doRun) {
			try {
				if (!communicationInterface.isConnected()) {
					throw new IllegalStateException("Connection lost!");
				}
				protocol.update();
			} catch (final Exception e) {
				e.printStackTrace();
				close(AbstractCommunicationInterface.ERROR);
				Log.log(Level.WARNING, getClass(), "run",
						"Exception while executing - maybe a timeout? Exception: {0}",
						e.getMessage());
				doRun = false;
			}
			try {
				Thread.sleep(10);
			} catch (final InterruptedException e) {
				// Won't happen
			}
		}
		if (isConnected()) {
			close(AbstractCommunicationInterface.NORMAL);
		}
	}

	public void setExecutingProgram(final boolean executingProgram) {
		this.executingProgram = executingProgram;
	}

	public void setProgram(final Program program) {
		this.program = program;
		this.program.init(this);
	}

	/**
	 * Sets the sensor update interval.
	 * 
	 * @param sensorUpdateInterval
	 *            the new sensor update interval
	 */
	public void setSensorUpdateInterval(final long sensorUpdateInterval) {
		if (sensorUpdateInterval <= 0) {
			throw new IllegalArgumentException(
					"Sensor update interval can't be less than 0, you wanted to set it to "
							+ sensorUpdateInterval);
		}
		if (BitOperations.isInt16((int) sensorUpdateInterval)) {
			Log.log(Level.INFO, getClass(), "setSensorUpdateInterval",
					"Setting sensor update interval to {0} ms!", sensorUpdateInterval);
			this.sensorUpdateInterval = sensorUpdateInterval;
			robot.queueOutgoingCommand(new Command(CommandType.SENSOR_UPDATE_INTERVAL, true,
					BitOperations.getHighByte((int) sensorUpdateInterval), BitOperations
							.getLowByte((int) sensorUpdateInterval)));
		} else {
			throw new IllegalArgumentException((int) sensorUpdateInterval + " is not an int16");
		}
	}

	/**
	 * Start.
	 */
	public void start() {
		if (doRun) {
			throw new IllegalStateException("Already running!");
		}
		doRun = true;
		final Thread thread = new Thread(this);
		thread.setName("RoboBridge Worker");
		thread.start();
	}
}
